import dayjs from "dayjs";

/**
 * 保存的历史数据的入参对象
 * code 数据项编码，与入库时定的唯一编码一致
 * statement 数据项说明
 * 必须包含其中一个: query 交易中心接口返回数据; save 入库的数据。
 */
export type HistoryDataObj = {
  code: string,
  statement: string,
  success?: boolean,
  [key: string]:any
} & ({ query: any } | { save: any });

/** 数据项的类型定义 */
interface DataHistory {
  id: string;           // 数据项的唯一标识符；与入库时定的唯一编码一致
  timestamp: number;    // 数据记录的时间戳
  timeStr: string; // YYYY-MM-DD HH:mm:ss
  logType: 'save' | 'query', // 查询或保存
  statement: string | null,
  success: boolean,
  // 数据内容，使用通用的键值对表示
  value: HistoryDataObj['save'] | HistoryDataObj['query'];
  version?: number;     // 数据版本（可选）
}

/** 数据库相关的配置类型定义 */
export type DBConfig = {
  dbName: string;       // 数据库名称
  version: number;      // 数据库版本号
  storeName: string;    // 数据存储对象名称
};

export class LogHistoryDB {
  private db: IDBDatabase | null = null; // 用于存储数据库连接实例
  private config: DBConfig;             // 数据库配置

  constructor(config: DBConfig) {
    this.config = config;
  }

  /** 初始化数据库连接，返回数据库实例 */
  async init(): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
      const { dbName, version, storeName } = this.config;
      const request = indexedDB.open(dbName, version); // 打开数据库

      // 数据库版本更新时触发（首次创建也会触发）
      request.onupgradeneeded = (event) => {
        const db = request.result;
        // 如果存储对象不存在，则创建存储对象
        if (!db.objectStoreNames.contains(storeName)) {
          const store = db.createObjectStore(storeName, {
            keyPath: "auto_id", // 使用自动生成的主键
            autoIncrement: true,
          });
          store.createIndex("id", "id", { unique: false }); // 创建索引，允许重复值
          store.createIndex("timestamp", "timestamp", { unique: false });
        }
      };

      // 数据库成功打开时的回调
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };

      // 数据库打开失败时的回调
      request.onerror = (event) => {
        reject(event.target); // 返回错误信息
      };
    });
  }

  /** 获取数据库实例，如果尚未连接则初始化 */
  private async getDB(): Promise<IDBDatabase> {
    if (this.db) return this.db;
    return await this.init();
  }

  /**
   * 创建通用事务，指定事务模式（只读或读写）
   * @param mode IDBTransactionMode
   * @returns 
   */
  private async getTransaction(mode: IDBTransactionMode): Promise<IDBObjectStore> {
    const db = await this.getDB();
    const transaction = db.transaction(this.config.storeName, mode); // 开启事务
    return transaction.objectStore(this.config.storeName); // 返回存储对象
  }

  /**
   * 添加数据（增）
   * @param data 要添加的数据对象，自动补充时间戳
   */
  private async add(data: DataHistory): Promise<void> {
    const store = await this.getTransaction("readwrite"); // 获取读写事务
    return new Promise((resolve, reject) => {
      const request = store.add(data); // 添加数据

      // 添加成功
      request.onsuccess = () => resolve();

      // 添加失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 在保存的历史数据对象中增加索引标记值（id,timestamp）
   * 返回最终需要存储的数据结构
   * @param data HistoryDataObj
   * @returns 
   */
  private generateHistoryData(data:HistoryDataObj):DataHistory {
    const {code = '', statement = null, success = true} = data
    return {
      id: code,
      timestamp: Date.now(),
      timeStr: dayjs().format('YYYY-MM-DD HH:mm:ss'),
      logType: data?.save ? 'save' : 'query',
      statement,
      success,
      value: data?.save || data?.query || null
    }
  }

  /**
   * 添加数据（增）
   * @param data 要添加的数据对象，自动补充时间戳
   */
  async addHistory(data: HistoryDataObj): Promise<void> {
    const saveData =  this.generateHistoryData(data)
    await this.add(saveData)
  }

  /**
   * 添加数据（增）
   * 1. 有些情况下数据是批量保存，即一次性保存多个数据项;
   * 2. 不同版本或保存的地址不同，数据结构也不同
   * @param data 要添加的数据对象，自动补充时间戳
   */
  async addMulitHistory(url:string, data:any, success = true): Promise<void> {
    const dataSaveTypes = ['doris/data/save', 'doris/data/save/v2', 'netrm/datachannel']
    if(!this.endsWithAny(url, dataSaveTypes)) return Promise.resolve()
    let saveData:any

    // 把几种不同的数据结构统一处理成 HistoryDataObj 结构
    if(this.endsWithAny(url,dataSaveTypes[0])) {
      const _data = {...(data?.[0]?.data?.[0]?.data || {})}
      const save = {
        ..._data,
        business_time: data?.[0]?.data?.[0]?.business_time || '',
      }
      saveData = {
        code: Object.keys(_data)[0].split('_')[0], // ahead、 realtime、 act
        statement:'doris/data/save 接口保存到大数据的多项公有数据',
        save,
        success
      } as HistoryDataObj
    }
    if(this.endsWithAny(url,dataSaveTypes[1])) {
      saveData = {
        code: data?.data_item, // ahead、 realtime、 act
        statement:'doris/data/save/v2 （新版）接口保存到大数据的单项公有数据',
        save:data,
        success
      } as HistoryDataObj
    }
    if(this.endsWithAny(url,dataSaveTypes[2])) {
      saveData = {
        code: data?.itemCode, // ahead、 realtime、 act
        statement:'netrm/datachannel 保存到后端（ETRM数据库）',
        save:data,
        success
      } as HistoryDataObj
    }

    await this.addHistory(saveData as HistoryDataObj)
  }

  /**
   * 判断字符是否以某几种字符串为结尾
   * @param targetString 目标字符串
   * @param searchWords 单串结尾字符串或字符串List
   * @returns 
   */
  endsWithAny(targetString:string, searchWords:string[] | string) {
    if (typeof searchWords === "string") {
      searchWords = [searchWords];
    }

    const patterns = new RegExp(`(${searchWords.join("|")})$`);
    return patterns.test(targetString);
  }

  /**
   * 判断字符是否以某几种字符串为开头
   * @param targetString 目标字符串
   * @param searchWords 单串开头字符串或字符串List
   * @returns 
   */
  startsWithAny(targetString:string, searchWords:string[] | string) {
    if (typeof searchWords === "string") {
      searchWords = [searchWords];
    }

    const patterns = new RegExp(`^(${searchWords.join("|")})`);
    return patterns.test(targetString);
  }

  /**
   * 获取符合指定 ID 的所有历史记录（查）
   * @param id 数据项的唯一标识符
   * @returns 符合条件的历史数据数组
   */
  async getAllById(id: string): Promise<DataHistory[]> {
    const store = await this.getTransaction("readonly"); // 获取只读事务
    const index = store.index("id"); // 使用 "id" 索引查找

    return new Promise((resolve, reject) => {
      const request = index.getAll(id); // 获取所有匹配数据

      // 查询成功
      request.onsuccess = (event) => resolve((event.target as IDBRequest).result);

      // 查询失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 按时间范围获取数据（查 范围）
   * @param startTime 开始时间戳
   * @param endTime 结束时间戳
   * @returns 时间范围内的历史数据数组
   */
  async getByTimeRange(startTime: number, endTime: number): Promise<DataHistory[]> {
    const store = await this.getTransaction("readonly"); // 获取只读事务
    const index = store.index("timestamp"); // 使用 "timestamp" 索引
    const range = IDBKeyRange.bound(startTime, endTime); // 创建范围查询

    return new Promise((resolve, reject) => {
      const request = index.openCursor(range); // 打开游标进行范围查询
      const results: DataHistory[] = [];

      // 游标移动并获取数据
      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
        if (cursor) {
          results.push(cursor.value); // 将数据添加到结果数组
          cursor.continue(); // 移动到下一个匹配项
        } else {
          resolve(results); // 游标遍历完成，返回结果
        }
      };

      // 查询失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 查询特定 ID 且在指定时间范围内的数据
   * @param id 数据项的唯一标识符
   * @param startTime 开始时间戳
   * @param endTime 结束时间戳
   * @returns 符合条件的历史数据数组
   */
  async getByIdAndTimeRange(id: string, startTime: number, endTime: number): Promise<DataHistory[]> {
    const store = await this.getTransaction("readonly"); // 获取只读事务
    const index = store.index("id"); // 使用 "id" 索引
    const range = IDBKeyRange.only(id); // 查询特定 ID

    return new Promise((resolve, reject) => {
      const request = index.openCursor(range); // 打开游标进行 ID 查询
      const results: DataHistory[] = [];

      // 游标移动并获取数据
      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
        if (cursor) {
          const record = cursor.value;
          // 检查时间戳是否在范围内
          if (record.timestamp >= startTime && record.timestamp <= endTime) {
            results.push(record);
          }
          cursor.continue(); // 继续处理下一个匹配项
        } else {
          resolve(results); // 游标遍历完成，返回结果
        }
      };

      // 查询失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 获取存储对象中的所有数据
   */
  async getAllData(): Promise<DataHistory[]> {
    const store = await this.getTransaction("readonly"); // 获取只读事务
  
    return new Promise((resolve, reject) => {
      const request = store.getAll(); // 获取所有数据
    
      // 获取成功
      request.onsuccess = (event) => resolve((event.target as IDBRequest<DataHistory[]>).result);
    
      // 获取失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 删除指定 ID 的所有历史记录（删）
   * @param id 数据项的唯一标识符
   */
  async deleteById(id: string): Promise<void> {
    const store = await this.getTransaction("readwrite"); // 获取读写事务
    const index = store.index("id"); // 使用 "id" 索引

    return new Promise((resolve, reject) => {
      const request = index.openCursor(IDBKeyRange.only(id)); // 打开游标定位指定 ID

      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
        if (cursor) {
          cursor.delete(); // 删除当前游标指向的记录
          cursor.continue(); // 继续删除下一条匹配记录
        } else {
          resolve(); // 删除完成
        }
      };

      // 删除失败
      request.onerror = (event) => reject(event.target);
    });
  }

  /**
   * 删除存储对象中的所有数据
   */
  async deleteAll(): Promise<void> {
    const store = await this.getTransaction("readwrite"); // 获取读写事务

    return new Promise((resolve, reject) => {
      const request = store.clear(); // 清空存储对象

      // 清空成功
      request.onsuccess = () => resolve();

      // 清空失败
      request.onerror = (event) => reject(event.target);
    });
  }
}

/** 数据库默认配置 */
export const DBConfig: DBConfig = {
  dbName: 'historyDatabase',      // 数据库名称
  version: 1,                    // 数据库版本
  storeName: 'dataHistoryStore', // 数据存储对象名称
};

/** 创建数据库实例并绑定到全局对象 */
const LogDBIntance = new LogHistoryDB(DBConfig)
self && (self.LogDB = LogDBIntance)
// window && (window.LogDB = LogDBIntance)
